﻿
namespace Csl.Caml.CamlPackage.Utilities
{
    using System;
    using System.IO;

    using EnvDTE;
    using EnvDTE80;

    using Microsoft.VisualStudio.TextTemplating.VSHost;

    /// <summary>
    /// VSPackage Utilities
    /// </summary>
    public class VsPackageUtils
    {
        /// <summary>
        /// Ensure a singleton
        /// </summary>
        private static readonly Lazy<VsPackageUtils> VsPackageUtilsSingleton
            = new Lazy<VsPackageUtils>(() => new VsPackageUtils());

        private DTE dte;

        private DTE2 dte2;

        private SolutionEvents solutionEvents;

        private DocumentEvents documentEvents;

        private ITextTemplating textTemplating;

        /// <summary>
        /// Prevents a default instance of the <see cref="VsPackageUtils" /> class from being created.
        /// </summary>
        private VsPackageUtils()
        {       
        }

        public void Initialise(DTE currentDte, DTE2 currentDte2, ITextTemplating currentTextTemplating)
        {
            if (currentDte == null)
            {
                throw new ArgumentNullException("currentDte");
            }

            if (currentDte2 == null)
            {
                throw new ArgumentNullException("currentDte2");
            }

            //if (textTemplating == null)
            //{
                //throw new ArgumentNullException("textTemplating");
            //}

            this.dte = currentDte;
            this.dte2 = currentDte2;
            this.textTemplating = currentTextTemplating;            

            this.solutionEvents = this.dte2.Events.SolutionEvents;
            this.documentEvents = this.dte2.Events.DocumentEvents;
        }

        /// <summary>
        /// Gets the VSPackageUtils instance.
        /// </summary>
        /// <value>
        /// The VSPackageUtils instance.
        /// </value>
        public static VsPackageUtils Instance
        {
            get
            {
                return VsPackageUtilsSingleton.Value;
            }
        }

        public SolutionEvents SolutionEvents
        {
            get
            {
                return this.solutionEvents;
            }
        }

        public DocumentEvents DocumentEvents
        {
            get
            {
                return this.documentEvents;
            }
        }

        public Solution2 CurrentSolution
        {
            get
            {
                return this.dte2.Solution as Solution2;
            }
        }

        public Project CurrentProject
        {
            get
            {
                // TODO: the concept of a current project doesn't make much sense
                // Does it mean the startup project? or the project whose file is currently selected in the solution explorer?
                // or the project which file is currently shown in the editor?
                throw new NotImplementedException();
                //var projects = (System.Array)this.applicationObject.DTE.ActiveSolutionProjects;
                //if (projects.Length != 1)
                //{
                //    return null;
                //}
                //var currentProject = (Project)projects.GetValue(0);
                //return currentProject;
                return null;
            }
        }

        public DTE Dte
        {
            get
            {
                return this.dte;
            }
        }

        public DTE2 Dte2
        {
            get
            {
                return this.dte2;
            }
        }

        public ITextTemplating TextTemplating
        {
            get
            {
                return this.textTemplating;
            }          
        }

        private string GetVsProjectItemTemplatePath(string itemName)
        {   
            if (string.IsNullOrEmpty(itemName))
            {
                throw new ArgumentNullException("itemName");
            }

            if (this.CurrentSolution == null)
            {
                throw new InvalidOperationException("No valid solution");
            }

            return this.CurrentSolution.GetProjectItemTemplate(itemName, "CSharp");
        }

        public static string GetFileName(ProjectItem projectItem)
        {
            return projectItem == null ? null : projectItem.FileNames[1];
        }

        public ProjectItem GetProjectItemByName(ProjectItems projectItems, string fileName, bool searchDescendents)
        {
            ProjectItem result = null;

            foreach (ProjectItem projectItem in projectItems)
            {
                if (projectItem.Name.Equals(fileName, StringComparison.OrdinalIgnoreCase))
                {
                    result = projectItem;
                    break;
                }

                if (searchDescendents)
                {
                    if (projectItem.ProjectItems != null && projectItem.ProjectItems.Count > 0)
                    {
                        result = this.GetProjectItemByName(projectItem.ProjectItems, fileName, true);
                    }
                }
            }

            return result;
        }

        public ProjectItem GetProjectItemByName(Project project, string fileName, bool searchDescendents)
        {
            return this.GetProjectItemByName(project.ProjectItems, fileName, searchDescendents);
        }        

        public AddFileResult AddNewProjectItemToProject(ProjectItem projectItem, string fileName, string fileContents = null, bool overwriteIfExists = false)
        {
            if (projectItem == null)
            {
                throw new ArgumentNullException("projectItem");
            }

            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentNullException("fileName");
            }
            
            var newProjectItem = this.GetProjectItemByName(projectItem.ProjectItems, fileName, true);
            var projectItemExists = newProjectItem != null;

            if (!projectItemExists)
            {
                // If the document doesn't exist in the project, then it may already exist on disk
                // So we would need to delete that file, otherwise we create a new file                
                
                // TODO: deal with files on disk, if file is on disk, it should be added to the project
                var combinedFilePath = Path.Combine(GetFileName(projectItem), fileName);
                var fileExistsOnDisk = File.Exists(combinedFilePath);

                if (fileExistsOnDisk)
                {
                    projectItem.ProjectItems.AddFromFileCopy(combinedFilePath);
                }
                else
                {
                    // AddFromTemplate always returns null, so we must search for the newly added file in the project items collection
                    // See http://stackoverflow.com/questions/5184311/no-reference-to-project-item-created-through-addfromtemplate-returned
                    //var textFilePath = this.GetVsProjectItemTemplatePath("Text File");
                    //projectItems.AddFromTemplate(textFilePath, fileName);
                    //projectItem = this.GetProjectItemByName(projectItems, fileName);   
                    using (var file = new StreamWriter(combinedFilePath))
                    {
                        file.Write(fileContents);
                    }

                    newProjectItem = projectItem.ProjectItems.AddFromFileCopy(combinedFilePath);
                }
            }

            if (newProjectItem == null)
            {
                return new AddFileResult(false);
            }

            var setFileContents = overwriteIfExists || !projectItemExists;

            // Set the document contents
            if (setFileContents)
            {
                this.SetDocumentText(newProjectItem.Document, fileContents);
            }

            return new AddFileResult(true, projectItem);
        }

        public ProjectItem AddFolder(Project project, string folderName)
        {
            return project.ProjectItems.AddFolder(folderName);
        }

        public ProjectItem AddFolder(ProjectItem projectItem, string folderName)
        {
            return projectItem.ProjectItems.AddFolder(folderName);
        }

        /// <summary>
        /// Sets the document text.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="content">The content.</param>
        public void SetDocumentText(Document document, string content)
        {
            if (document == null)
            {
                return;
            }

            var textSelection = document.Selection as TextSelection;

            if (textSelection == null)
            {
                return;
            }

            textSelection.SelectAll();
            textSelection.Delete();
            textSelection.Insert(content);
            textSelection.StartOfDocument();
        }

        //public ProjectItem GetParentProjectItem(ProjectItem projectItem)
        //{
        //    if (projectItem == null)
        //    {
        //        throw new ArgumentNullException("projectItem");
        //    }

        //    projectItem.

        //    var documentPath = projectItem.Document.Path.Trim('\\');
        //    var parentPath = string.Format("{0}\\", documentPath.Substring(0, documentPath.LastIndexOf(Path.DirectorySeparatorChar)));
        //    var parentProjectItem = this.GetProjectItemByName(projectItem.ContainingProject.ProjectItems, parentPath);
        //    return parentProjectItem;
        //}

        public ProjectItem GetParentProjectItem(ProjectItems projectItems)
        {
            if (projectItems == null)
            {
                return null;
            }

            var parentProjectItem = projectItems.Parent as ProjectItem;
            return parentProjectItem;
        }

        public ProjectItem GetParentProjectItem(ProjectItem projectItem)
        {
            if (projectItem == null)
            {
                return null;
            }

            var siblings = this.GetProjectItemSiblingsAndSelf(projectItem);
            return this.GetParentProjectItem(siblings);
        }

        public ProjectItems GetProjectItemSiblingsAndSelf(ProjectItem projectItem)
        {
            if (projectItem == null)
            {
                throw new ArgumentNullException("projectItem");
            }

            return projectItem.Collection;
        }
    }

    public class AddFileResult : OperationResult
    {
        public AddFileResult(bool success, ProjectItem addedProjectItem = null) : base(success)
        {
            this.AddedProjectItem = addedProjectItem;
        }

        public ProjectItem AddedProjectItem { get; private set; }        
    }

    public class OperationResult
    {
        public bool WasSuccessful { get; private set; }
        
        public OperationResult(bool success)
        {
            this.WasSuccessful = success;
        }
    }
}